#lang racket

(define (acc op initial sequence)
  (if (null? sequence)
      initial
      (op (car sequence)
          (acc op initial (cdr sequence)))))

(define (=number? exp num)
  (and (number? exp) (= exp num)))

(define (variable? x) (symbol? x))

(define (same-variable? v1 v2)
  (and (variable? v1) (variable? v2) (eq? v1 v2)))

(define (sum? x)
  (and (pair? x) (eq? (car x) '+)))

(define (product? x)
  (and (pair? x) (eq? (car x) '*)))

(define (exponentiation? x)
  (and (pair? x) (eq? (car x) '^)))

(define (addend s) (cadr s))

(define (augend s)
  (if (null? (cdddr s))
      (caddr s)
      (cons '+ (cddr s))))

(define (multiplier p) (cadr p))

(define (multiplicand p)
  (if (null? (cdddr p))
      (caddr p)
      (cons '* (cddr p))))

(define (base e) (cadr e))

(define (exponent e) (caddr e))

(define (make-sum a1 a2 . a3)
  (let ((ads (append (list a1 a2) a3)))
    (let ((nums (filter (lambda (x) (number? x)) ads))
          (not-nums (filter (lambda (x) (not (number? x))) ads)))
      (let ((num-result (acc + 0 nums)))
        (cond ((null? not-nums) num-result)
              ((= num-result 0) (if (null? (cdr not-nums))
                                    (car not-nums)
                                    not-nums))
              (else (append (list '+ num-result) not-nums)))))))

(define (make-product m1 m2 . m3)
  (let ((ads (append (list m1 m2) m3)))
    (let ((nums (filter (lambda (x) (number? x)) ads))
          (not-nums (filter (lambda (x) (not (number? x))) ads)))
      (let ((num-result (acc * 1 nums)))
        (cond ((= num-result 0) 0)
              ((null? not-nums) num-result)
              ((= num-result 1) (if (null? (cdr not-nums))
                                    (car not-nums)
                                    not-nums))
              (else (append (list '* num-result) not-nums)))))))

(define (make-exponentiation base exp)
  (cond ((= exp 0) 1)
        ((= exp 1) base)
        (else (list '^ base exp))))

(define (deriv exp var)
  (cond ((number? exp) 0)
        ((variable? exp)
         (if (same-variable? exp var) 1 0))
        ((sum? exp)
         (make-sum (deriv (addend exp) var)
                   (deriv (augend exp) var)))
        ((product? exp)
         (make-sum
          (make-product (multiplier exp)
                        (deriv (multiplicand exp) var))
          (make-product (deriv (multiplier exp) var)
                        (multiplicand exp))))
        ((exponentiation? exp)
         (make-product (exponent exp)
                       (make-exponentiation (base exp) (- (exponent exp) 1))
                       (deriv (base exp) var)))
        (else
         (error "unknown expression"))))

(deriv '(^ (+ x x) 3) 'x)

(deriv '(+ x 3) 'x)

(deriv '(* x y) ' x)